// vPresent is an immersive presentation creation and display application.
// Copyright (C) 2007 by Infiscape Corporation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

#include "WindowSettings.h"

#include <cstdlib>
#include <sstream>
#include <stdexcept>

#if WINDOW_SYS == X11
#  include <X11/cursorfont.h>
#  include <X11/Xatom.h>
#  include <GL/glx.h>
#endif

#include <OpenSG/OSGConfig.h>
#if WINDOW_SYS == X11
#  include <OpenSG/OSGXWindow.h>
#elif WINDOW_SYS == COCOA
#  include <OpenSG/OSGCocoaWindow.h>
#elif WINDOW_SYS == WINDOWS
#  include <OpenSG/OSGWIN32Window.h>
#else
#  error "Unknown window system in use"
#endif

#include "Window.h"


#if WINDOW_SYS == X11
namespace
{

template<int EventType>
int waitForEvent(Display*, XEvent* event, char* arg)
{
   return event->type == EventType && event->xmap.window == (::Window) arg;
}

}
#endif

namespace vp
{

Window::Window()
   : mFullScreen(false)
   , mX(0)
   , mY(0)
   , mDone(false)
#if WINDOW_SYS == X11
   , mDisplay(NULL)
   , mColormap(0)
   , mXWindow(0)
   , mVisualInfo(NULL)
#endif
{
   /* Do nothing. */ ;
}

WindowPtr Window::init(int& argc, char** argv, const bool fullScreen,
                       const bool alwaysOnTop, const bool stereo,
                       const std::string& name, const int x, const int y,
                       const unsigned int width, const unsigned int height)
{
   mFullScreen  = fullScreen;
   mX           = x;
   mY           = y;

   mWindow = createWindow(argc, argv, fullScreen, alwaysOnTop, stereo, name,
                          x, y, width, height);

   return shared_from_this();
}

Window::~Window()
{
   // Clear this out first since it references mXWindow and mDisplay.
   mWindow = OSG::NullFC;

#if WINDOW_SYS == X11
   if ( NULL != mDisplay && 0 != mXWindow )
   {
      XDestroyWindow(mDisplay, mXWindow);
   }

   if ( NULL != mDisplay && 0 != mColormap )
   {
      XFreeColormap(mDisplay, mColormap);
   }

   if ( NULL != mDisplay )
   {
      XCloseDisplay(mDisplay);
      mDisplay = NULL;
   }
#endif
}

boost::signals::connection Window::connectRenderSlot(render_slot_t slot)
{
   return mRenderSignal.connect(slot);
}

void Window::show()
{
#if WINDOW_SYS == X11
   XEvent event;

   XMapWindow(mDisplay, mXWindow);
   XIfEvent(mDisplay, &event, waitForEvent<MapNotify>, (char*) mXWindow);

   if ( mFullScreen )
   {
      XMoveWindow(mDisplay, mXWindow, 0, 0);
      XResizeWindow(mDisplay, mXWindow,
                    DisplayWidth(mDisplay, mVisualInfo->screen),
                    DisplayHeight(mDisplay, mVisualInfo->screen));

      XDefineCursor(mDisplay, mXWindow, mEmptyCursor);
   }
   else
   {
      // For some reason, passing in mX and mY to XCreateWindow() (see
      // createWindow()) does not put the window at that location.
      XMoveWindow(mDisplay, mXWindow, mX, mY);
   }

   XFlush(mDisplay);
   XRaiseWindow(mDisplay, mXWindow);
#endif
}

void Window::hide()
{
#if WINDOW_SYS == X11
   XUnmapWindow(mDisplay, mXWindow);
   XEvent event;
   XIfEvent(mDisplay, &event, waitForEvent<UnmapNotify>, (char*) mXWindow);

   // TODO: Restore the regular mouse cursor?
#endif
}

void Window::close()
{
   mDone = true;
}

const OSG::WindowRefPtr Window::createWindow(int& argc, char** argv,
                                             const bool fullScreen,
                                             const bool alwaysOnTop,
                                             const bool stereo,
                                             const std::string& name,
                                             const int x, const int y,
                                             const unsigned int width,
                                             const unsigned int height)
{
   OSG::WindowRefPtr window_ptr;

#if WINDOW_SYS == X11
   int dbl_buffered[16];

   dbl_buffered[0] = GLX_RGBA;
   dbl_buffered[1] = GLX_DEPTH_SIZE;
   dbl_buffered[2] = 16;
   dbl_buffered[3] = GLX_DOUBLEBUFFER;
   dbl_buffered[4] = stereo ? GLX_STEREO : None;
   dbl_buffered[5] = None;

   GLboolean double_buffer = GL_FALSE;

   // Open the display specified by the DISPLAY environment variable.
   mDisplay = XOpenDisplay(NULL);

   if ( NULL == mDisplay )
   {
      std::ostringstream msg_stream;
      msg_stream << "Error: Could not open display ";

      char* display_env = std::getenv("DISPLAY");

      if ( NULL != display_env )
      {
         msg_stream << "'" << display_env << "'";
      }
      else
      {
         msg_stream << "<NULL>";
      }

      msg_stream << "!";

      throw std::runtime_error(msg_stream.str());
   }

   int dummy;

   if ( ! glXQueryExtension(mDisplay, &dummy, &dummy) )
   {
      std::ostringstream msg_stream;
      msg_stream << "Error: X server has no OpenGL GLX extension!";
      throw std::runtime_error(msg_stream.str());
   }

   mVisualInfo = glXChooseVisual(mDisplay, DefaultScreen(mDisplay),
                                 dbl_buffered);

   if ( NULL == mVisualInfo )
   {
      int single_buffered[] = { GLX_RGBA, GLX_DEPTH_SIZE, 16, None };
      mVisualInfo = glXChooseVisual(mDisplay, DefaultScreen(mDisplay),
                                    single_buffered);

      if ( NULL == mVisualInfo )
      {
         std::ostringstream msg_stream;
         msg_stream << "No RGB visual with depth buffer!";
         throw std::runtime_error(msg_stream.str());
      }

      double_buffer = GL_FALSE;
   }

   if ( mVisualInfo->c_class != TrueColor )
   {
      std::ostringstream msg_stream;
      msg_stream << "TrueColor visual required for this program!";
      throw std::runtime_error(msg_stream.str());
   }

   mColormap = XCreateColormap(mDisplay,
                               RootWindow(mDisplay, mVisualInfo->screen),
                               mVisualInfo->visual, AllocNone);

   XSetWindowAttributes swa;
   swa.colormap     = mColormap;
   swa.border_pixel = 0;
   swa.event_mask   = ExposureMask | StructureNotifyMask;

   // Create Window

   // Create a Window and connect it to the main display.
   mXWindow = XCreateWindow(mDisplay,
                            RootWindow(mDisplay, mVisualInfo->screen), x, y,
                            width, height, 0, mVisualInfo->depth, InputOutput,
                            mVisualInfo->visual,
                            CWBorderPixel | CWColormap | CWEventMask, &swa);

   std::ostringstream win_name;
   win_name << "vPresent: " << name;
   XSetStandardProperties(mDisplay, mXWindow, win_name.str().c_str(),
                          "vPresent", None, argv, argc, NULL);

   if ( fullScreen )
   {
      // Set the decoration hints.
      Atom no_decor_atom = XInternAtom(mDisplay, "_MOTIF_WM_HINTS", 0);

      if ( no_decor_atom == None )
      {
         std::cerr << "Could not intern X atom for _MOTIF_WM_HINTS."
                   << std::endl;
      }

      struct NoDecorHints
      {
         long flags;
         long functions;
         long decorations;
         long input_mode;
      };

      NoDecorHints hints;

      hints.flags = 2;
      hints.decorations = 0;

      XChangeProperty(mDisplay, mXWindow, no_decor_atom, no_decor_atom, 32,
                      PropModeReplace, (unsigned char*) &hints, 4);

//      Atom fullscreen_hint = XInternAtom(mDisplay,
//                                         "_NET_WM_STATE_FULLSCREEN", 0);
      // Create an empty cursor.
      static char data[1] = {0};

      Pixmap blank;
      XColor dummyCol;

      blank = XCreateBitmapFromData(mDisplay, mXWindow, data, 1, 1);

      mEmptyCursor = XCreatePixmapCursor(mDisplay, blank, blank, &dummyCol,
                                         &dummyCol, 0, 0);

      XFreePixmap(mDisplay, blank);
   }

   if ( alwaysOnTop )
   {
      Atom net_wm_state = XInternAtom(mDisplay, "_NET_WM_STATE", 0);

      Atom above_hint = XInternAtom(mDisplay, "_NET_WM_STATE_ABOVE", 0);
      Atom always_hint = XInternAtom(mDisplay,
                                     "_NET_WM_STATE_STAYS_ON_TOP", 0);

      Atom net_winstates[] = { above_hint, always_hint };
      XChangeProperty(mDisplay, mXWindow, net_wm_state, XA_ATOM, 32,
                      PropModeReplace, (unsigned char*) net_winstates, 2);
   }

   OSG::XWindowRefPtr xwin(OSG::XWindow::create());
   xwin->setDisplay(mDisplay);
   xwin->setWindow(mXWindow);
   xwin->init();
   window_ptr = OSG::WindowRefPtr(xwin);
#endif

   return window_ptr;
}

void Window::reshape(const int width, const int height)
{
   mWindow->resize(width, height);
}

void Window::runEventLoop()
{
#if WINDOW_SYS == X11
   int ip;
   XEvent event;

   mDone = false;

   while ( ! mDone )
   {
      while ( ip = XPending(mDisplay) )
      {
         XNextEvent(mDisplay, &event);

         switch ( event.type )
         {
            case ConfigureNotify:
               reshape(event.xconfigure.width, event.xconfigure.height);
               break;
            case Expose:
               mRenderSignal();
               break;
         }
      }

      mRenderSignal();
   }
#endif
}

}
